#include "index.h"
#include "operatingsystem.h"

#include <unistd.h>
#include <sys/param.h>

extern string executable;
extern bool verbose;

std::string exec(string cmd)
{
    FILE* pipe = POPEN(cmd.c_str(), "r");
    if (!pipe)
    {
        cerr << "Could not executable";
    }
    char buffer[128];
    std::string result = "";
    while(!feof(pipe)) 
    {
        if(fgets(buffer, 128, pipe) != NULL)
            result += buffer;
    }
    PCLOSE(pipe);
    return result;
}

std::string readFile(string filename)
{
    string contents = "";
    string line;
    ifstream myfile(filename.c_str());
    if (myfile.is_open())
    {
        while (! myfile.eof() )
        {
            getline(myfile, line);
            contents += line + "\n";
        }
        myfile.close();
    }
    else 
    {
        std::cerr << "Unable to open file \"" + filename + "\"" << std::endl;
    }

    return contents;
}

void writeFile(string filename, string &contents)
{
    //TODO: create all subdirectories that don't exist

    std::ofstream myfile;
    myfile.open(filename.c_str());
    if(!myfile.is_open())
    {
        std::cerr << "Unable to write file \"" + filename + "\"" << std::endl;
        return;
    }
    myfile << contents;
    myfile.close();
}



void trim(string &str)
{
  string::size_type pos = str.find_last_not_of(" \n\t\r");
  if(pos != string::npos) {
    str.erase(pos + 1);
    pos = str.find_first_not_of(" \n\t\r");
    if(pos != string::npos) str.erase(0, pos);
  }
  else str.erase(str.begin(), str.end());
}

bool fileReadable(string filename)
{
    ifstream myfile(filename.c_str());
    if (myfile.is_open())
    {
        myfile.close();
        return true;
    }
    else 
    {
        return false;
    }
}

int ls(string dir, vector<string> &files)
{
    DIR *dp;
    struct dirent *dirp;
    if((dp  = opendir(dir.c_str())) == NULL) {
        cerr << "Error(" << errno << ") opening " << dir << endl;
        return errno;
    }

    while ((dirp = readdir(dp)) != NULL) {
        if (string(dirp->d_name) == ".")
            continue;
        if (string(dirp->d_name) == "..")
            continue;
        files.push_back(string(dirp->d_name));
    }
    closedir(dp);
    return 0;
}


void parsePathConfig(string location, vector<string> &paths)
{
    if (verbose)
        std::cerr << "Indexing location " << location << std::endl;
        
    string line;
    ifstream myfile(location.c_str());
    if (myfile.is_open())
    {
        while (!myfile.eof())
        {
            getline(myfile, line);
            trim(line);
            if (line.substr(0, 1) != "#" && line != "")
            {
                paths.push_back(line);
            }
        }
        myfile.close();
    }
    else 
    {
        std::cerr << "Unable to open file \"" + location + "\"" << std::endl;
    }
}

string absPath(string path)
{
    if (path.substr(0, 1)[0] == PATH_SEPARATOR)
        return path;

    char cwd[MAXPATHLEN];
    char* res = getcwd(cwd, MAXPATHLEN);
    res = 0; // hack to prevent warning on g++;
    string first = string(cwd);
    if (first.substr(first.length() - 1)[0] != PATH_SEPARATOR)
        first += PATH_SEPARATOR;
    return first + path;
}

void buildIndex()
{
    vector<string> paths;
    string index = "";
    string filename = "/etc/thinder/modindex.cache";
    
    cout << "Indexing modules..." << endl;
    
    if (getenv("THINDER_MOD_PATH") != 0)
        paths.push_back(absPath(string(getenv("THINDER_MOD_PATH"))));
    
    vector<string> locations;
    
    locations.push_back("/etc/thinder/paths.conf");
    locations.push_back("~/.thinder/paths.conf");
    
    for (unsigned int i = 0; i < locations.size(); i++)
        if (fileReadable(locations[i]))
            parsePathConfig(locations[i], paths);
            
    for (unsigned int i = 0; i < paths.size(); i++)
        indexPath(paths[i], index);
    
    writeFile(filename, index);
}

void indexPath(string &path, string &index)
{
    vector<string> files;
    if (ls(path, files) != 0)
        return;
    if (path.substr(path.length() - 1)[0] != PATH_SEPARATOR)
        path += PATH_SEPARATOR;
    for (unsigned int i = 0; i < files.size(); i++)
    {
        string filename = path + files[i];
        cerr << "    " << filename << "\n";
        string cache = exec(executable + " --cache \"" + filename + "\"");
        index += ">" + filename + "\n" + cache;
    }
}

vector<string> findName(string location, string name)
{
    string index = "\n" + readFile(location);
    vector<string> files;
    
    unsigned int found = 0;
    size_t pos = 0;
    while ((pos = index.find("\t" + name + "\n", pos)) != string::npos)
    {
        // found a declaration of 'name'
        ++found;
        size_t pos2 = index.substr(0, pos).rfind("\n>");
        if (pos2 == string::npos)
            break;
        string filename = index.substr(pos2 + 2, index.substr(pos2 + 2).find("\n"));
        if (fileReadable(filename))
            files.push_back(filename);
        pos++;
    }
    
    return files;
}

void printPaths()
{
    vector<string> paths;    
    
    if (getenv("THINDER_MOD_PATH") != 0)
        paths.push_back(absPath(string(getenv("THINDER_MOD_PATH"))));
    
    vector<string> locations;
    locations.push_back("/etc/thinder/paths.conf");
    locations.push_back("~/.thinder/paths.conf");
    
    for (unsigned int i = 0; i < locations.size(); i++)
        if (fileReadable(locations[i]))
            parsePathConfig(locations[i], paths);
    
    for (unsigned int i = 0; i < paths.size(); i++)
        std::cout << paths[i] << std::endl;
}

void whereIs(string name)
{
    vector<string> locations;
    vector<string> files;
    
    if (getenv("THINDER_CACHE_PATH") != 0)
        locations.push_back(string(getenv("THINDER_CACHE_PATH")));
        
    locations.push_back("/etc/thinder/modindex.cache");
    locations.push_back("~/.thinder/modindex.cache");
    
    if (verbose)
    {
        std::cerr << "Search locations:" << std::endl;
        for (unsigned int i = 0; i < locations.size(); i++)
            std::cerr << locations[i] << std::endl;
        std::cerr << std::endl;
    }
    
    for (unsigned int i = 0; i < locations.size(); i++)
        if (fileReadable(locations[i]))
        {
            vector<string> tempfiles = findName(locations[i], name);
            files.insert(files.end(), tempfiles.begin(), tempfiles.end());
        }
    
    if (files.size() == 0)
        std::cerr << "Name \"" + name + "\" is not found.\nHint: use verbose mode to see a list of search locations." << std::endl;
    if (files.size() > 1)
        std::cerr << "Warning: name is defined in multiple modules.\nSee the documentation for directions." << std::endl;
    for (unsigned int i = 0; i < files.size(); i++)
    {
        std::cout << files[i] << std::endl;
    }
}

